﻿using System;
using System.Collections.Generic;
using System.Dataflow;
using System.IO;
using System.Linq;
using Microsoft.M;
using Simp.Model.Exceptions;
using Simp.Model.CoreExpressions;
using Simp.Model.CoreExpressions.Sequences;

namespace Simp.Model.Parsing
{
	/// <summary>Implementation of <see cref="IParser"/> using Oslo Textual DSL APIs.</summary>
	public class OsloParser : IParser
	{
		private readonly DynamicParser _grammarParser;

		/// <summary>Constructor.</summary>
		public OsloParser()
		{	//	Find the compiled grammar file resource
			string resourceName = string.Concat(
				GetType().Assembly.GetName().Name,
				".Parsing.OsloComponents",
				".Simp.mx");

			using (Stream stream = GetType().Assembly.GetManifestResourceStream(resourceName))
			{
				MImage grammar = new MImage(stream, false);

				_grammarParser = grammar.ParserFactories["Simp.Dsl.Simp"].Create();
				_grammarParser.GraphBuilder = new NodeGraphBuilder();
			}
		}

		#region IParser Members
		void IParser.Parse(string content, out IExpression[] expressionDefinitionList, out IExpression returnExpression)
		{
			if (string.IsNullOrEmpty(content))
			{
				throw new ArgumentNullException("content");
			}
			try
			{
				using (StringReader reader = new StringReader(content))
				using (TextReaderTextStream stream = new TextReaderTextStream(reader))
				{
					object parsingResult = _grammarParser.Parse(stream, null);

					if (parsingResult != null)
					{
						Node rootNode = (Node)parsingResult;
						List<IExpression> customExpressions = new List<IExpression>();
						IExpression expression = null;
						var firstLevelCollection = rootNode.ViewAllNodes();

						foreach (var firstLevelNode in firstLevelCollection)
						{
							switch (GetNodeName(firstLevelNode))
							{
								case "FunctionDefinition":
									IExpression functionDefinition = LoadExpressionDefinition(firstLevelNode, false);

									customExpressions.Add(functionDefinition);
									break;
								case "Return":
									Node returnValueNode = firstLevelNode.ViewAllNodes().First();

									expression = LoadExpression(returnValueNode);
									break;
								default:
									throw new NotImplementedException();
							}
						}

						returnExpression = expression;
						expressionDefinitionList = customExpressions.ToArray();
					}
					else
					{
						throw new ParsingException();
					}
				}
			}
			catch (ParsingException)
			{
				throw;
			}
			catch (Exception ex)
			{
				throw new ParsingException("Error during parsing", ex);
			}
		}
		#endregion

		private static IExpression LoadExpression(Node functionNode)
		{
			switch (GetNodeName(functionNode))
			{
				case "FunctionCall":
					return LoadExpressionCall(functionNode);
				case IntegerExpression.IntegerExpressionName:
					return LoadInteger(functionNode.ViewAllNodes().First());
				case RealExpression.RealExpressionName:
					return LoadReal(functionNode.ViewAllNodes().First());
				case ExplicitSequenceExpression.ExplicitSequenceExpressionName:
					return LoadExplicitSequence(functionNode);
				case IntervalSequenceExpression.IntervalSequenceExpressionName:
					return LoadIntervalSequence(functionNode);
				case DerivedSequenceExpression.ExplicitSequenceExpressionName:
					return LoadDerivedSequence(functionNode);
				case RangeSelectorExpression.RangeSelectorExpressionName:
					return LoadRangeSelector(functionNode);
				case ElementSelectorExpression.ElementSelectorExpressionName:
					return LoadElementSelector(functionNode);
				case "Lambda":
					return LoadExpressionDefinition(functionNode, true);
				case "FunctionScope":
					return LoadExpressionScope(functionNode);
				default:
					throw new NotSupportedException(GetNodeName(functionNode));
			}
		}

		private static IExpression LoadExpressionScope(Node functionNode)
		{
			List<IExpression> expressionList = new List<IExpression>();
			IExpression returnExpression = null;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "FunctionDefinition":
						expressionList.Add(LoadExpressionDefinition(node, false));
						break;
					case "Return":
						returnExpression = LoadExpression(node.ViewAllNodes().First());
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			expressionList.Add(returnExpression);

			IExpression scopeExpression = new CompositeExpression(
				new ScopeExpression(expressionList.Count),
				expressionList.ToArray());

			return scopeExpression;
		}

		private static IExpression LoadRangeSelector(Node functionNode)
		{
			IExpression source = null;
			IExpression first = null;
			IExpression last = null;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "Source":
						source = LoadExpression(node.ViewAllNodes().First());
						break;
					case "First":
						first = LoadExpression(node.ViewAllNodes().First());
						break;
					case "Last":
						last = LoadExpression(node.ViewAllNodes().First());
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			return new CompositeExpression(new RangeSelectorExpression(), source, first, last);
		}

		private static IExpression LoadElementSelector(Node functionNode)
		{
			IExpression source = null;
			IExpression index = null;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "Source":
						source = LoadExpression(node.ViewAllNodes().First());
						break;
					case "Index":
						index = LoadExpression(node.ViewAllNodes().First());
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			return new CompositeExpression(new ElementSelectorExpression(), source, index);
		}

		private static IExpression LoadDerivedSequence(Node functionNode)
		{
		    IExpression source = null;
		    IExpression transform = null;

		    foreach (Node node in functionNode.ViewAllNodes())
		    {
		        switch (GetNodeName(node))
		        {
		            case "Source":
		                source = LoadExpression(node.ViewAllNodes().First());
		                break;
		            case "Transform":
		                transform = LoadExpression(node.ViewAllNodes().First());
		                break;
		            default:
		                throw new NotSupportedException(GetNodeName(node));
		        }
		    }

			return new CompositeExpression(new DerivedSequenceExpression(), source, transform);
		}

		private static IExpression LoadIntervalSequence(Node functionNode)
		{
			IExpression first = null;
			IExpression last = null;

			foreach (Node node in functionNode.ViewAllNodes())
			{
				switch (GetNodeName(node))
				{
					case "First":
						first = LoadExpression(node.ViewAllNodes().First());
						break;
					case "Last":
						last = LoadExpression(node.ViewAllNodes().First());
						break;
					default:
						throw new NotSupportedException(GetNodeName(node));
				}
			}

			return new CompositeExpression(new IntervalSequenceExpression(), first, last);
		}

		private static IExpression LoadExplicitSequence(Node functionNode)
		{
			List<IExpression> subExpressionList = new List<IExpression>();

			foreach (Node subNode in functionNode.ViewAllNodes())
			{
				IExpression subFunction = LoadExpression(subNode);

				subExpressionList.Add(subFunction);
			}

			return new CompositeExpression(
				new ExplicitSequenceExpression(subExpressionList.Count),
				subExpressionList.ToArray());
		}

		private static IExpression LoadExpressionDefinition(Node functionNode, bool isLambda)
		{
		    string name = isLambda ? "Lambda" : null;
		    IExpression body = null;
			List<IExpression> parameterList = new List<IExpression>();

		    foreach (Node node in functionNode.ViewAllNodes())
		    {
		        switch (GetNodeName(node))
		        {
		            case "Name":
		                name = GetNodeValue(node);
		                break;
		            case "Args":
						parameterList = LoadExpressionDefinitionParameters(node);
						break;
		            case "Body":
		                Node innerNode = node.ViewAllNodes().First();

		                body = LoadExpression(innerNode);
		                break;
		            default:
		                throw new NotSupportedException(GetNodeName(node));
		        }
		    }

			parameterList.Insert(0, body);

			CustomExpression customFunction = new CustomExpression(name, isLambda, parameterList.Count);
			CompositeExpression composite = new CompositeExpression(customFunction, parameterList.ToArray());

			return composite;
		}

		private static List<IExpression> LoadExpressionDefinitionParameters(Node argsNode)
		{
			List<IExpression> paramDefinitionList = new List<IExpression>();

			foreach (Node parameterNode in argsNode.ViewAllNodes())
			{
				string parameterName = null;
				IExpression parameterDefaultValue = null;

				if (GetNodeName(parameterNode) != "ParameterDefinition")
				{
					throw new NotSupportedException(GetNodeName(parameterNode));
				}
				foreach (Node subNode in parameterNode.ViewAllNodes())
				{
					switch (GetNodeName(subNode))
					{
						case "Name":
							parameterName = GetNodeValue(subNode);
							break;
						case "Default":
							parameterDefaultValue = LoadExpression(subNode.ViewAllNodes().First());
							break;
						default:
							throw new NotSupportedException(GetNodeName(subNode));
					}
				}
				if (parameterDefaultValue != null)
				{	//	Add a ParameterBindingExpression
					paramDefinitionList.Add(new CompositeExpression(
						new ParameterBindingExpression(),
						new StringExpression(parameterName),
						parameterDefaultValue));
				}
				else
				{	//	Just add a string as the parameter name
					paramDefinitionList.Add(new StringExpression(parameterName));
				}
			}

			return paramDefinitionList;
		}

		private static IExpression LoadExpressionCall(Node functionCallNode)
		{
		    string expressionName = null;
			IExpression[] parameters = ExpressionHelper.EmptyExpressionList;

		    foreach (Node node in functionCallNode.ViewAllNodes())
		    {
		        switch (GetNodeName(node))
		        {
		            case "Name":
		                expressionName = GetNodeValue(node);
		                break;
		            case "Args":
		                parameters = LoadExpressionCallParameters(node);
		                break;
		            default:
		                throw new NotSupportedException();
		        }
		    }

			return new CompositeExpression(
				new UnboundCallingExpression(expressionName, parameters.Length),
				parameters);
		}

		private static IExpression[] LoadExpressionCallParameters(Node argsNode)
		{
			List<IExpression> bindingList = new List<IExpression>();

			foreach (Node parameterNode in argsNode.ViewAllNodes())
			{
				string parameterName = null;
				IExpression parameterValue = null;

				if (GetNodeName(parameterNode) != "Parameter")
				{
					throw new NotSupportedException(GetNodeName(parameterNode));
				}
				foreach (Node subNode in parameterNode.ViewAllNodes())
				{
					switch (GetNodeName(subNode))
					{
						case "Name":
							parameterName = GetNodeValue(subNode);
							break;
						case "Value":
							parameterValue = LoadExpression(subNode.ViewAllNodes().First());
							break;
						default:
							throw new NotSupportedException();
					}
				}
				if (parameterName == null)
				{
					bindingList.Add(parameterValue);
				}
				else
				{
					IExpression binding = new CompositeExpression(
						new ParameterBindingExpression(),
						new StringExpression(parameterName),
						parameterValue);

					bindingList.Add(binding);
				}
			}

			return bindingList.ToArray();
		}

		private static IExpression LoadInteger(Node integerNode)
		{
			int intValue = int.Parse(GetNodeValue(integerNode));

			return new IntegerExpression(intValue);
		}

		private static IExpression LoadReal(Node realNode)
		{
			double doubleValue = double.Parse(GetNodeValue(realNode));

			return new RealExpression(doubleValue);
		}

		private static string GetNodeName(Node node)
		{
			if (node.NodeKind == NodeKind.Atomic)
			{
				DefaultGraphStoreConstantNode constantNode = (DefaultGraphStoreConstantNode)node.StoreKey;

				return (string)constantNode.Brand.Text;
			}
			else
			{
				return node.Brand.Text;
			}
		}

		private static string GetNodeValue(Node node)
		{
			if (node.NodeKind == NodeKind.Atomic)
			{
				return node.AtomicValue.ToString();
			}
			else
			{
				Node valueNode = node.ViewAllNodes().First();

				return valueNode.AtomicValue.ToString();
			}
		}
	}
}